// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Linq;
using FluentAssertions;
using Nethermind.Core.Crypto;
using Nethermind.Core.Test.Builders;
using NUnit.Framework;

namespace Nethermind.Core.Test
{
    [TestFixture]
    public class BloomTests
    {
        [Test]
        public void Test()
        {
            Bloom bloom = new();
            bloom.Set(Keccak.OfAnEmptyString.Bytes);
            ReadOnlySpan<byte> bytes = bloom.Bytes;
            Bloom bloom2 = new(bytes);
            Assert.That(bloom2.ToString(), Is.EqualTo(bloom.ToString()));
        }

        [TestCase(1, 1)]
        [TestCase(1, 10)]
        [TestCase(10, 1)]
        [TestCase(10, 10)]
        [TestCase(100, 1)]
        public void matches_previously_added_item(int count, int topicMax)
        {
            MatchingTest(() => GetLogEntries(count, topicMax), addedEntries => addedEntries, true);
        }

        [TestCase(1, 1)]
        [TestCase(1, 10)]
        [TestCase(10, 1)]
        [TestCase(10, 10)]
        [TestCase(100, 1)]
        public void does_not_match_not_added_item(int count, int topicMax)
        {
            MatchingTest(() => GetLogEntries(count, topicMax),
                addedEntries => GetLogEntries(count, topicMax,
                addedEntries.Sum(a => a.Topics.Length)), false);
        }

        [Test]
        public void empty_does_not_match_any_item()
        {
            MatchingTest(Array.Empty<LogEntry>, static addedEntries => GetLogEntries(100, 10), false);
        }

        public void MatchingTest(Func<LogEntry[]> addedEntries, Func<LogEntry[], LogEntry[]> testedEntries, bool isMatchExpectation)
        {
            Bloom bloom = new();
            var entries = addedEntries();
            bloom.Add(entries);

            var testEntries = testedEntries(entries);
            var results = testEntries.Select(e => bloom.Matches(e));
            results.Should().AllBeEquivalentTo(isMatchExpectation);
        }

        [TestCase(1, 1, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000002000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000400000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
        [TestCase(1, 10, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000002000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000400000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
        [TestCase(10, 1, "80000000000000000001020000000004000000800000020000000000000000000000000000000002000000080100000000000000206000000000000000040000000002000000400000000000088080000000000000000000200000000200000800000040000000000000004000000000000000002002001000080200000000000000100000000000000020000004000000000000000000000200000400000040000000000020000000000020000000000000000040000420000000000000000000000000000000000000000300008022040408000000100800000100000000000801080000000000020004000000000044000000000200000000000000000180")]
        [TestCase(10, 10, "80000000000000000001020000000004000000800000020000001020004002000800000000000002000000080100000000000000206000000000000000040000000002000000400000000080088080002000000000000000200400000200004840000040000000000200004000040000000000002002001040080200000000000000102004000000002020000044000020000000000000000200000400000040000000000020000000080020000008000400000040000420004000000000000000000000000000000400000300008022040408000000100800000100000000000801080000000000020004080000000044000000000200000000180000000580")]
        [TestCase(10, 100, "80000000000000000001020000000004000000800000020000001020004002000800000000000002000000080100000000000000206000000000000000040000000002000000400000000080088080002000000000000000200400000200004840000040000000000200004000040000000000002002001040080200000000000000102004000000002020000044000020000000000000000200000400000040000000000020000000080020000008000400000040000420004000000000000000000000000000000400000300008022040408000000100800000100000000000801080000000000020004080000000044000000000200000000180000000580")]
        [TestCase(100, 1, "80390020254dcc0401935a020004808510401a82110282925040142005c502502870c22001000043111400580f2418500071020ca46840228038841080864500011002a1112cc8000849048d4e9081312148008c09c99214200c10000f001848e1483cc070a018300360a040082e8080212880562492201042880304005046826214d0a4566a5229052c648400d6001e30530050640a800c022202841c8041c19c4010090221a48c331c503001401c0306d80060410567a6066040140b0c527050e0008c100803840c804d3b60008022650c8c1021809608002021409048030209010c80851400d202a08498110c4d40e402481020cb090420a49c51000117a7")]
        public void Produces_expected_value(int count, int topicMax, string expectedValue)
        {
            Bloom bloom = new();
            bloom.Add(GetLogEntries(count, topicMax));

            bloom.ToString().Should().Be(expectedValue);
        }

        private static LogEntry[] GetLogEntries(int count, int topicsMax, int start = 0)
        {
            var keccakGenerator = TestItem.Keccaks;
            var entries = new LogEntry[count];
            for (int i = start; i < count + start; i++)
            {
                int topicsCount = i % topicsMax + 1;
                var topics = new Hash256[topicsCount];
                for (int j = 0; j < topicsCount; j++)
                {
                    topics[j] = keccakGenerator[i + j];
                }

                entries[i - start] = new LogEntry(TestItem.Addresses[i], [], topics);
            }

            return entries;
        }
    }
}
